﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Security.Permissions;
using System.Security.Principal;
using System.Threading;
using System.Web;
using System.Web.Configuration;
using System.Web.Security;
using System.Web.SessionState;
using YawetagLib.Security;
using YawetagLib.Utilities;

namespace YawetagLib.Web.Security
{
    public static class AuthenticationHelper
    {
        [SecurityPermission(SecurityAction.Demand, ControlPrincipal = true)]
        public static void LoadAuthenticationData(IUser user, Guid sessionID, string[] roles)
        {
            if (!IsUserContext)
            {
                FormsAuthenticationTicket ticket = GetAuthenticationTicketFromUser(user, sessionID, roles, true, DateTime.Now.AddMinutes(5));
                IIdentity identity = GetIdentity(ticket);

                if (identity != null)
                {
                    SetUserContext(new UserContext(identity, user, sessionID, roles));
                }
            }
        }

        [SecurityPermission(SecurityAction.Demand, ControlPrincipal = true)]
        public static void LoadAuthenticationData(IUserLocator userLocator = null)
        {
            if (!IsUserContext)
            {
                FormsAuthenticationTicket ticket = GetAuthenticationTicketFromCookie();
                IIdentity identity = GetIdentity(ticket);

                if (ticket != null && identity != null)
                {
                    string[] userData = ticket.UserData.Split('|');

                    int userID = int.Parse(userData[0]);
                    string identifier = userData[1];
                    Guid sessionID = new Guid(userData[2]);
                    string[] roles = userData[3].Split('*');

                    SetUserContext(new UserContext(identity, userID, identifier, sessionID, roles, userLocator));
                }
            }
        }

        public static void StoreAuthenticationData(IUser user, Guid sessionID, string[] roles, bool isPersistent)
        {
            DateTime expires = GetTicketExpiration();
            FormsAuthenticationTicket ticket = GetAuthenticationTicketFromUser(user, sessionID, roles, isPersistent, expires);
            HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket))
            {
                Domain = FormsAuthentication.CookieDomain, Path = FormsAuthentication.FormsCookiePath, Expires = expires
            };

            HttpContext.Current.Response.Cookies.Add(cookie);

            LoadAuthenticationData(user, sessionID, roles);
        }

        public static void AbandonSession()
        {
            HttpSessionState session = HttpContext.Current.Session;
            if (session != null)
            {
                session.Abandon();
            }

            FormsAuthentication.SignOut();
        }

        public static FormsAuthenticationTicket GetAuthenticationTicket()
        {
            return GetAuthenticationTicketFromCookie();
        }

        private static IIdentity GetIdentity(FormsAuthenticationTicket ticket)
        {
            return IsFormsAuthentication 
                ? (ticket != null ? new FormsIdentity(ticket) : null)
                : (HttpContext.Current.User != null ? HttpContext.Current.User.Identity : null);
        }

        private static void SetUserContext(IPrincipal userContext)
        {
            Thread.CurrentPrincipal = userContext;
            HttpContext.Current.User = userContext;
        }

        private static DateTime GetTicketExpiration()
        {
            if (IsFormsAuthentication)
            {
                double expirationMinutesTimeout = ((AuthenticationSection)ConfigurationManager
                    .GetSection("system.web/authentication")).Forms.Timeout.TotalMinutes;

                return DateTime.Now.AddMinutes(expirationMinutesTimeout);
            }
            
            return DateTime.Now.AddYears(100);
        }

        private static FormsAuthenticationTicket GetAuthenticationTicketFromUser(
            IUser user, Guid sessionID, IEnumerable<string> roles, bool isPersistent, DateTime expires)
        {
            string userData = user.UserID 
                + "|" + user.Identifier 
                + "|" + sessionID 
                + "|" + roles.ToDelimitedSequence("*");

            return new FormsAuthenticationTicket(1, user.Identifier, DateTime.Now, expires, isPersistent, userData);
        }

        private static FormsAuthenticationTicket GetAuthenticationTicketFromCookie()
        {
            if (IsFormsIdentity)
            {
                return ((FormsIdentity)HttpContext.Current.User.Identity).Ticket;
            }

            HttpCookie cookie = HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];

            return cookie != null ? FormsAuthentication.Decrypt(cookie.Value) : null;
        }

        public static bool UserIsAuthenticated
        {
            get { return UserExists && HttpContext.Current.User.Identity.IsAuthenticated; }
        }

        private static bool IsUserContext
        {
            get { return UserExists && HttpContext.Current.User is UserContext; }
        }

        private static bool IsFormsAuthentication
        {
            get
            {
                return ((AuthenticationSection)ConfigurationManager
                    .GetSection("system.web/authentication")).Mode == AuthenticationMode.Forms;
            }
        }

        private static bool IsFormsIdentity
        {
            get { return UserExists && HttpContext.Current.User.Identity is FormsIdentity; }
        }

        private static bool UserExists
        {
            get { return HttpContext.Current.User != null; }
        }
    }
}
